#include <strmif.h>
#include "mkvcontext.hpp"
#include "mediatypeutil.hpp"
#include <amvideo.h>
#include <dvdmedia.h>
#include <cassert>
#include <uuids.h>

namespace MkvMux
{

MkvStreamVideo::VideoFrame::VideoFrame()
{
}



MkvStreamVideo::MkvStreamVideo(
    MkvContext& c,
    const AM_MEDIA_TYPE& mt) :
    MkvStream(c)
{
    MediaTypeUtil::Copy(mt, m_mt);
}


MkvStreamVideo::~MkvStreamVideo()
{
    MediaTypeUtil::Destroy(m_mt);
}


const BITMAPINFOHEADER& MkvStreamVideo::GetBitmapInfoHeader() const
{
    const AM_MEDIA_TYPE& mt = m_mt;
    assert(mt.majortype == MEDIATYPE_Video);
    assert(mt.pbFormat);

    if (mt.formattype == FORMAT_VideoInfo)
    {
        assert(mt.cbFormat >= sizeof(VIDEOINFOHEADER));
        
        VIDEOINFOHEADER& vih = (VIDEOINFOHEADER&)(*mt.pbFormat);
        BITMAPINFOHEADER& bmih = vih.bmiHeader;
        assert(bmih.biSize >= sizeof(BITMAPINFOHEADER));
        
        return bmih;
    }   
    
    if (mt.formattype == FORMAT_VideoInfo2)
    {
        assert(mt.cbFormat >= sizeof(VIDEOINFOHEADER2));
        
        VIDEOINFOHEADER2& vih = (VIDEOINFOHEADER2&)(*mt.pbFormat);
        BITMAPINFOHEADER& bmih = vih.bmiHeader;
        assert(bmih.biSize >= sizeof(BITMAPINFOHEADER));
        
        return bmih;
    }
    
    assert(mt.formattype == FORMAT_MPEG2_VIDEO);
    
    {    
        assert(mt.subtype == MEDIASUBTYPE_MPEG2_VIDEO);
        assert(mt.cbFormat >= sizeof(VIDEOINFOHEADER2));

        const MPEG2VIDEOINFO& mpg = (MPEG2VIDEOINFO&)(*mt.pbFormat);
        assert(mt.cbFormat >= SIZE_MPEG2VIDEOINFO(&mpg));

        const VIDEOINFOHEADER2& vih = mpg.hdr;
        const BITMAPINFOHEADER& bmih = vih.bmiHeader;
        assert(bmih.biSize >= sizeof(BITMAPINFOHEADER));

        //http://dvd.sourceforge.net/dvdinfo/mpeghdrs.html#seq
        
        return bmih;
    }
}


float MkvStreamVideo::GetFramerate() const
{
    const AM_MEDIA_TYPE& mt = m_mt;
    assert(mt.majortype == MEDIATYPE_Video);
    assert(mt.pbFormat);

    if (mt.formattype == FORMAT_VideoInfo)
    {
        assert(mt.cbFormat >= sizeof(VIDEOINFOHEADER));        
        VIDEOINFOHEADER& vih = (VIDEOINFOHEADER&)(*mt.pbFormat);
        
        if (vih.AvgTimePerFrame <= 0)
            return 0;
            
        //[ticks/sec] / [ticks/frame] = [frames/sec]
        const float result = 10000000.0f / float(vih.AvgTimePerFrame);
        
        return result;
    }   
    
    if (mt.formattype == FORMAT_VideoInfo2)
    {
        assert(mt.cbFormat >= sizeof(VIDEOINFOHEADER2));        
        VIDEOINFOHEADER2& vih = (VIDEOINFOHEADER2&)(*mt.pbFormat);
        
        if (vih.AvgTimePerFrame <= 0)
            return 0;
            
        //[ticks/sec] / [ticks/frame] = [frames/sec]
        const float result = 10000000.0f / float(vih.AvgTimePerFrame);
        
        return result;
    }
    
    if (mt.formattype == FORMAT_MPEG2_VIDEO)    
    {    
        assert(mt.subtype == MEDIASUBTYPE_MPEG2_VIDEO);
        assert(mt.cbFormat >= sizeof(VIDEOINFOHEADER2));

        const MPEG2VIDEOINFO& mpg = (MPEG2VIDEOINFO&)(*mt.pbFormat);
        assert(mt.cbFormat >= SIZE_MPEG2VIDEOINFO(&mpg));

        const VIDEOINFOHEADER2& vih = mpg.hdr;

        if (vih.AvgTimePerFrame <= 0)
            return 0;
            
        //[ticks/sec] / [ticks/frame] = [frames/sec]
        const float result = 10000000.0f / float(vih.AvgTimePerFrame);
        
        return result;
    }

    return 0;
}


void MkvStreamVideo::WriteTrackType()
{
    EbmlIO::File& f = m_context.m_file;
    
    f.WriteID1(0x83);     //track type
    f.Write1UInt(1);
    f.Serialize1UInt(1);  //1=video
}


bool MkvStreamVideo::Wait() const
{
    return m_context.WaitVideo();
}


MkvStreamVideo::frames_t& MkvStreamVideo::GetFrames()
{
    return m_vframes;
}


MkvStreamVideo::frames_t& MkvStreamVideo::GetKeyFrames()
{
    return m_rframes;
}


void MkvStreamVideo::VideoFrame::Write(
    const MkvStream& s,
    ULONG cluster_timecode_ms) const
{
    EbmlIO::File& file = s.m_context.m_file;

    //block = 1 byte ID + 4 byte size + f->size
    //block duration = 1 byte ID + 1 byte size + 1 byte value
    //reference block = 1 byte ID + 2 byte size + 2(?) byte signed value

    const ULONG block_size = 1 + 2 + 1 + size();     //tn, tc, flg, f
    const ULONG block_group_size = 1 + 4 + block_size; //block id, size, payload

    file.WriteID1(0xA0);                //block group ID
    file.Write4UInt(block_group_size);  //size of payload for this block group    

#ifdef _DEBUG    
    const __int64 pos = file.GetPosition();
#endif

    //begin block

    file.WriteID1(0xA3);    //SimpleBlock ID
    file.Write4UInt(block_size);

    const int tn_ = s.GetTrackNumber();
    assert(tn_ > 0);
    assert(tn_ <= 127);

    const BYTE tn = static_cast<BYTE>(tn_);

    file.Write1UInt(tn);   //track number

    const ULONG ft = curr_timecode_ms();
      
    {
        const LONG tc_ = LONG(ft) - LONG(cluster_timecode_ms);
        assert(tc_ >= SHRT_MIN);
        assert(tc_ <= SHRT_MAX);

        const SHORT tc = static_cast<SHORT>(tc_);

        file.Serialize2SInt(tc);       //relative timecode 
    }

    BYTE flags = 0;   

    if (IsKey())
        flags |= BYTE(1 << 7);

    file.Write(&flags, 1);   //written as binary, not uint

    file.Write(data(), size());  //frame

    //end block

#ifdef _DEBUG
    const __int64 newpos = file.GetPosition();
    assert((newpos - pos) == block_group_size);
#endif

}


void MkvStreamVideo::Flush()
{
    m_context.FlushVideo(this);
}



}  //end namespace MkvMux


